package maple.core;

import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.Vector;

import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

import maple.lang.Command;
import maple.lang.Language;
import maple.util.Planner;
import maple.util.Task;
import maple.util.tasks.ApplicationTask;
import maple.util.tasks.MasterTask;
import maple.util.Timestamp;
import maple.util.Tuple;

public abstract class Application extends Consumer
{
	protected final int INTERVAL = 1000;

	protected int _size; 
	protected int _index;
	protected Tuple[] _tuples; 
	
	protected HashMap< String, Tuple > _perceptions;
	
	protected HashMap< String, ApplicationTask > _subtasks;
	protected HashMap< String, ApplicationTask > _tasks;
	
	public Application( final int size, final int number )
	{
		super( Language.Instruction.SUBSCRIBE, number );
		
		_index = 0;
		_size = size;
		
		_perceptions = new HashMap< String, Tuple >();
		_subtasks = new HashMap< String, ApplicationTask >();
		_tasks = new HashMap< String, ApplicationTask >();
	}

	protected void actionInform( final ACLMessage message )
	{
		String[] tokens = message.getContent().split( Language.DELIMITER );
		if( tokens.length < 2 )
		{
			// the result of the set command
			String command = message.getUserDefinedParameter( Task.Property.COMMAND.toString() );
			if( _subtasks.containsKey( command ) )
			{
				ApplicationTask task = _subtasks.get( command );
				if( tokens[ 0 ].equals( Command.Message.OK.toString() ) )
				{
					task.setPlan( command, true );
				}
				else
				{
					task.setPlan( command, false );
				}

				if( task.isFinished() )
				{
					Command.Message result = Command.Message.NOT_FOUND;	

					if( task.isDispatched() )
					{
						result = Command.Message.OK;

						for( String plan : task.setInterval( "" + INTERVAL ) )
						{
							sendMessage( ACLMessage.REQUEST, task.getMaster(), plan, null, null );
						}
						task.setTriggerTime( System.currentTimeMillis() );
						_tasks.put( task.toString(), task );
					}

					// send the reply message of the subscribe command
					HashMap< String, String > parameters = new HashMap< String, String >();
					parameters.put( Task.Property.COMMAND.toString(), task.getCommand() );
					sendMessage( ACLMessage.INFORM, task.getSender(), result.toString(), parameters, null );
				}

				_subtasks.remove( command );
			}
		}
		else
		{
			try
			{
				// receive the perceptions
				String postcondition = message.getUserDefinedParameter( Planner.Property.POSTCONDITION.toString() );
				String location = message.getUserDefinedParameter( MasterTask.Property.LOCATION.toString() );

				String type = location + Language.DELIMITER + postcondition;
				Tuple tuple = new Tuple( Timestamp.Default.parse( tokens[ 0 ] ).getTime(), type, tokens[ 1 ] );
				_perceptions.put( type, tuple );

				// store in the buffer
				_index = _index % _size;
				_tuples[ _index ] = tuple;
				++_index;
			}
			catch( NumberFormatException e )
			{
				_logger.log( Level.WARNING, "", e );
			}
			catch( ParseException e )
			{
				_logger.log( Level.WARNING, "", e );
			}
		}
	}

	protected void actionRequest( final ACLMessage message )
	{
		//_logger.info( message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		System.out.println( Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		
		String sender = message.getSender().getName();
		String content = message.getContent();
		String receiver = message.getReplyWith();
		if( receiver == null )
		{
			receiver = sender;
		}

		String result = Command.Message.BAD_COMMAND.toString();
		Command command = new Command( content );
		if( command != null && command.isAvailable() )
		{
			String master = findMaster();
			ApplicationTask task = new ApplicationTask( sender, command, receiver, master );
			if( task.isAvailable() )
			{
				String key = task.toString();
				if( _tasks.containsKey( key ) )
				{
					result = undo( _tasks.get( key ) ).toString();
					System.out.println( message.getSender().getLocalName() + Language.DELIMITER +Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + content + " STOP!" );
					_logger.info( message.getSender().getLocalName() + Language.DELIMITER + content + " STOP!" );
				}
				else
				{
					Vector< String > locations = task.getLocations();
					if( locations.size() == _preconditions.size() )
					{
						Vector< String > plans = new Vector< String >();
						
						// check the preconditions
						for( int i = 0; i < _preconditions.size(); ++i )
						{
							Command set = new Command( Language.Instruction.SET.toString() 
													   + Language.DELIMITER
													   + MasterTask.Property.GOAL.toString()
													   + Language.ASSIGN
													   + Language.QUOTE
													   + _preconditions.get( i )
													   + Language.QUOTE
													   + Language.DELIMITER
													   + MasterTask.Property.INTERVAL.toString()
													   + Language.ASSIGN
													   + Language.QUOTE
													   + "-1"
													   + Language.QUOTE
													   + Language.DELIMITER
													   + MasterTask.Property.LOCATION.toString()
													   + Language.ASSIGN
													   + Language.QUOTE
													   + locations.get( i )
													   + Language.QUOTE );

							String plan = set.toString();
							plans.add( plan );
							_subtasks.put( plan, task );

							sendMessage( ACLMessage.REQUEST, master, plan, null, null );
						}

						task.setPlans( plans );
						result = null;

						System.out.println( message.getSender().getLocalName() + Language.DELIMITER + Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + content + " START!" );
						_logger.info( message.getSender().getLocalName() + Language.DELIMITER + content + " START!" );
					}
				}
			}
		}

		// error occur or undo
		if( result != null )
		{
			HashMap< String, String > parameters = new HashMap< String, String >();
			parameters.put( Task.Property.COMMAND.toString(), content );
			sendMessage( ACLMessage.INFORM, sender, result, parameters, null );
		}
	}

	protected void check()
	{
		for( ApplicationTask task : _tasks.values() )
		{
			long current = System.currentTimeMillis();
			if( ( current - task.getTriggerTime() ) > 1000 )
			{
				task.setTriggerTime( current );
				subscribe( task );
			}
		}
	}

	/**
	 * return a random number
	 *
	 * @param	max	the random number is less than the this number
	 * @return	a random number
	 *
	 * @author	snowfoxy
	 * @since	2011-11-15
	 */
	private int choose( final int max )
	{
		return ( int )( Math.random() * max );
	}

	/**
	 * return one of the master
	 *
	 * @return	one of the master
	 *
	 * @author	snowfoxy
	 * @since	2011-12-09
	 */
	protected String findMaster()
	{
		ServiceDescription service = new ServiceDescription();
		service.setName( Language.Instruction.SET.toString() );
		DFAgentDescription[] candidate = listAllAgents( _arguments[ 0 ], service );
		if( candidate != null )
		{
			int length = candidate.length;
			if( length > 0 )
			{
				return candidate[ choose( length ) ].getName().getName();
			}
		}
		return null;
	}

	/**
	 * return the String representation of the operator
	 *
	 * @return	the String representation of the operator
	 *
	 * @author	snowfoxy
	 * @since	2011-11-25
	 */
	protected String toOperator()
	{
		String operator = getName() + Language.DELIMITER + _operator;
		for( ApplicationTask.Property property : ApplicationTask.Property.values() )
		{
			operator = operator 
				       + Language.DELIMITER
					   + property.toString()
					   + Language.ASSIGN
					   + Language.QUOTE
					   + Language.QUOTE;
		}
		return operator; 
	}

	/**
	 * return the status of removing the specified task
	 *
	 * @param	task	the specified task
	 * @return	the status of removing the specified task
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	protected Command.Message undo( final ApplicationTask task )
	{
		for( String plan : task.setInterval( "" + INTERVAL ) )
		{
			sendMessage( ACLMessage.REQUEST, task.getMaster(), new Command( plan ).toString(), null, null ); 
		}
		_tasks.remove( task.toString() );
		return Command.Message.OK;
	}

	protected abstract void subscribe( final ApplicationTask task );
}
